/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Description:
============
*/

#include "commonheaders.h"
#include "dlgPropSheet.h"
#include "m_skin.h"

namespace NContactDetailsPS {

/**
 * name:	BoldGroupTitlesEnumChildren()
 * desc:	set font of groupboxes to bold
 *
 * return:	0
 **/
BOOL CALLBACK BoldGroupTitlesEnumChildren( HWND hWnd, LPARAM lParam )
{
   TCHAR szClass[64];
   GetClassName( hWnd, szClass, 64 );
   if( !mir_tcscmp( szClass, _T( "Button" ) ) && ( GetWindowLongPtr( hWnd, GWL_STYLE ) & 0x0F ) == BS_GROUPBOX )
      SendMessage( hWnd, WM_SETFONT, lParam, NULL );
   return TRUE;
}

/**
 * name:	~CPsTreeItem
 * class:	CPsTreeItem
 * desc:	default constructor
 * param:	pPsh	- propertysheet's init structure
 *			odp		- optiondialogpage structure with the info about the item to add
 * return:	nothing
 **/
CPsTreeItem::CPsTreeItem()
{
	_idDlg = NULL;
	_pTemplate = NULL;
	_hInst = NULL;
	_pfnDlgProc = NULL;
	_hWnd = NULL;
	_dwFlags = NULL;
	_hItem = NULL;			// handle to the treeview item
	_iParent = -1;			// index to the parent item
	_iImage = -1;			// index of treeview item's image
	_bState = NULL;			// initial state of this treeitem
	_pszName = NULL;		// original name, given by plugin ( not customized )
	_ptszLabel = NULL;
	_pszProto = NULL;
	_pszPrefix = NULL;
	_hContact = NULL;
}

/**
 * name:	~CPsTreeItem
 * class:	CPsTreeItem
 * desc:	default destructor
 * param:	none
 * return:	nothing
 **/
CPsTreeItem::~CPsTreeItem()
{
	if( _hWnd ) DestroyWindow( _hWnd );
	if( _pszName ) mir_free( ( LPVOID )_pszName );
	if( _ptszLabel ) mir_free( ( LPVOID )_ptszLabel );
	if( _pszProto ) mir_free( ( LPVOID )_pszProto );
}

/**
 * name:	PropertyKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty - the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::PropertyKey( LPCSTR pszProperty )
{
	static CHAR szSetting[MAXSETTING];
	mir_snprintfA( szSetting, SIZEOF( szSetting ), "{%s\\%s}_%s", _pszPrefix, _pszName, pszProperty );
	return szSetting;
}

/**
 * name:	GlobalName
 * class:	CPsTreeItem
 * desc:	return item name without prepended protocol name
 * param:	nothing
 * return:	item name without protocol name
 **/
LPCSTR CPsTreeItem::GlobalName()
{
	LPSTR pgn = NULL;
	
	if( _dwFlags & PSPF_PROTOPREPENDED )
	{
		pgn = mir_strchr( _pszName, '\\' );
		if( pgn && pgn[1] ) pgn++;
	}
	return ( !pgn || !*pgn ) ?_pszName : pgn;
}

/**
 * name:	GlobalPropertyKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty - the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::GlobalPropertyKey( LPCSTR pszProperty )
{
	static CHAR szSetting[MAXSETTING];
	mir_snprintfA( szSetting, SIZEOF( szSetting ), "{Global\\%s}_%s", GlobalName(), pszProperty );
	return szSetting;
}

/**
 * name:	IconKey
 * class:	CPsTreeItem
 * desc:	merge the treeitem's unique name with a prefix to form a setting string
 * param:	pszProperty - the prefix to add
 * return:	pointer to the setting string
 **/
LPCSTR CPsTreeItem::IconKey()
{
	LPCSTR pszIconName = GlobalName();
	if( pszIconName )
	{
		static CHAR szSetting[MAXSETTING];
		mir_snprintfA( szSetting, SIZEOF( szSetting ), MODNAME"_{%s}", pszIconName );
		return szSetting;
	}
	return NULL;
}

/**
 * name:	ParentItemName()
 * class:	CPsTreeItem
 * desc:	returns the unique name for the parent item
 * param:	nothing
 * return:	length of group name
 **/
LPSTR CPsTreeItem::ParentItemName()
{
	DBVARIANT dbv;
	LPSTR result;

	// try to read the parent item from the database
	if( !DB::Setting::GetUString( NULL, MODNAME, PropertyKey( SET_ITEM_GROUP ), &dbv ) )
	{
		result = dbv.pszVal;
	}
	else 
	{
		const CHAR* p = mir_strrchr( _pszName, '\\');
		
		if( p ) 
		{
			INT cchGroup = p - _pszName + 1;
			result = mir_strncpy( ( LPSTR )mir_alloc( cchGroup ), _pszName, cchGroup );
		}
		else
		{
			result = NULL;
		}
	}
	return result;
}

/**
 * name:	SetName
 * class:	CPsTreeItem
 * desc:	set the unique name for this item from a given title as it comes with OPTIONDIALOGPAGE
 * param:	ptszTitle	- the title which is the base for the unique name
 *			bIsUnicode	- if TRUE the title is unicode
 * return:	0 on success, 1 to 4 indicating the failed operation
 **/
INT CPsTreeItem::Name( LPTSTR ptszTitle, const BOOLEAN bIsUnicode )
{
	// convert title to utf8
	_pszName = ( bIsUnicode ) ? mir_utf8encodeW( ( LPWSTR )ptszTitle ) : mir_utf8encode( ( LPSTR )ptszTitle );
	if( _pszName )
	{
		// convert disallowed characters
		for( DWORD i = 0; _pszName[i] != 0; i++ ) 
		{
			switch( _pszName[i] ) 
			{
				case '{': _pszName[i] = '('; break;
				case '}': _pszName[i] = ')'; break;
			}
		}
	}
	return _pszName == NULL;
}

/**
 * name:	HasName
 * class:	CPsTreeItem
 * desc:	returns true, if current item has the name provided by the parameter
 * params:	pszName	- the name to match the item with
 * return:	nothing
 **/
BOOLEAN	CPsTreeItem::HasName( const LPCSTR pszName ) const
{ 
	return !mir_stricmp( _pszName, pszName ); 
};

/**
 * name:	Label
 * class:	CPsTreeItem
 * desc:	set new label for the treeitem
 * params:	pszLabel	- the desired new label
 * return:	nothing
 **/
VOID CPsTreeItem::Rename( const LPTSTR pszLabel )
{
	if( pszLabel && *pszLabel ) 
	{
		LPTSTR pszDup = mir_tcsdup( pszLabel );
		if( pszDup ) 
		{
			if( _ptszLabel )
			{
				mir_free( _ptszLabel );
			}
			_ptszLabel = pszDup; 
			// convert disallowed characters
			while( *pszDup ) 
			{
				switch( *pszDup ) 
				{
					case '{': *pszDup = '('; break;
					case '}': *pszDup = ')'; break;
				}
				pszDup++;
			}
			AddFlags( PSTVF_LABEL_CHANGED );
		}
	}
}

/**
 * name:	ItemLabel
 * class:	CPsTreeItem
 * desc:	returns the label for a given item. The returned value must be freed after use!
 * param:	pszName		- uniquely identifiing string for a propertypage encoded with utf8 ( e.g.: {group\item} )
 * return:	Label in a newly allocated piece of memory
 **/
INT CPsTreeItem::ItemLabel( const BOOLEAN bReadDBValue )
{
	DBVARIANT dbv;

	// clear existing
	if( _ptszLabel )
	{
		mir_free( _ptszLabel );
	}

	// try to get custom label from database
	if( !bReadDBValue || DB::Setting::GetTString( NULL, MODNAME, GlobalPropertyKey( SET_ITEM_LABEL ), &dbv ) || ( _ptszLabel = dbv.ptszVal ) == NULL )
	{
		// extract the name
		LPSTR pszName = mir_strrchr( _pszName, '\\' );
		if( pszName && pszName[1] )
		{
			pszName++;
		}
		else
		{
			pszName = _pszName;
		}

		LPTSTR ptszLabel = mir_utf8decodeT( pszName );
		if( ptszLabel ) 
		{
			_ptszLabel = mir_tcsdup( TranslateTS( ptszLabel ) );
			mir_free( ptszLabel );
		}
	}
	// return nonezero if label is invalid
	return _ptszLabel == NULL;
}

/**
 * name:	ProtoIcon
 * class:	CPsTreeItem
 * desc:	check if current tree item name is a protocol name and return its icon if so
 * params:	none
 * return:	nothing
 **/
HICON CPsTreeItem::ProtoIcon()
{
	PROTOCOLDESCRIPTOR **pd;
	INT ProtoCount, i;

	if( !CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&ProtoCount, ( LPARAM )&pd ) )
	{
		LPSTR pszName = Translate( _pszName );
		DWORD cchName = mir_strlen( pszName );

		if( pszName )
		{
			for( i = 0; i < ProtoCount; i++ ) 
			{
				if( pd[i]->type == PROTOTYPE_PROTOCOL )
				{
					LPSTR p = Translate( pd[i]->szName );

					if( !mir_strnicmp( p, pszName, mir_strlen( p ) ) ) 
					{
						HICON hIco;
						CHAR szIconID[MAX_PATH];

						mir_snprintfA( szIconID, SIZEOF( szIconID ), "core_status_%s1", pd[i]->szName );
						hIco = NIcoLib::GetIcon( szIconID );
						if( !hIco )
						{
							hIco = ( HICON )CallProtoService( pd[i]->szName, PS_LOADICON, PLI_PROTOCOL, NULL );
						}
						return hIco;
					}
				}
			}
		}
	}
	return NULL;
}

/**
 * name:	Icon
 * class:	CPsTreeItem
 * desc:	load the icon, add to icolib if required and add to imagelist of treeview
 * params:	hIml			- treeview's imagelist to add the icon to
 *			odp				- pointer to OPTIONSDIALOGPAGE providing the information about the icon to load
 *			hDefaultIcon	- default icon to use
 * return:	nothing
 **/
INT CPsTreeItem::Icon( HIMAGELIST hIml, OPTIONSDIALOGPAGE *odp, BOOLEAN bInitIconsOnly )
{
	HICON hIcon;

	// check parameter
	if( !_pszName || !odp )
		return 1;

	// load the icon if no icolib is installed or creating the required settingname failed
	LPCSTR pszIconName = IconKey();

	// use icolib to handle icons
	if( !( hIcon = NIcoLib::GetIcon( pszIconName ) ) ) 
	{
		SKINICONDESC sid;

		ZeroMemory( &sid, sizeof( sid ) );
		sid.cbSize = sizeof( sid );
		sid.flags = SIDF_ALL_TCHAR;
		sid.cx = GetSystemMetrics( SM_CXSMICON );
		sid.cy = GetSystemMetrics( SM_CYSMICON );
		sid.pszName = ( LPSTR )pszIconName;
		sid.ptszDescription = _ptszLabel;
		sid.ptszSection = TranslateT( SECT_TREE );

		// the item to insert brings along an icon?
		if( odp->flags & ODPF_ICON ) {
			// is it uinfoex item?
			if( odp->hInstance == ghInst ) {

				// the pszGroup holds the iconfile for items added by uinfoex
				sid.ptszDefaultFile = odp->ptszGroup;

				// icon library exists?
				if( sid.ptszDefaultFile ) {
					sid.iDefaultIndex = ( INT )odp->hIcon;
				}
				// no valid icon library
				else {
					sid.hDefaultIcon = ImageList_GetIcon( hIml, 0, ILD_NORMAL );;
					sid.iDefaultIndex = -1;
				}
			}
			// default icon is delivered by the page to add
			else {
				sid.hDefaultIcon = ( odp->hIcon ) ? odp->hIcon : ImageList_GetIcon( hIml, 0, ILD_NORMAL );
				sid.iDefaultIndex = -1;
			}
		}
		// no icon to add, use default
		else {
			sid.iDefaultIndex = -1;
			sid.hDefaultIcon = ProtoIcon();
			if( !sid.hDefaultIcon ) sid.hDefaultIcon = ImageList_GetIcon( hIml, 0, ILD_NORMAL );
		}
		// add file to icolib
		CallServiceSync( MS_SKIN2_ADDICON, NULL, ( LPARAM )&sid );

		if( !bInitIconsOnly )
		{
			hIcon = NIcoLib::GetIcon( pszIconName );
		}
	}
	
	if( !bInitIconsOnly && hIml ) {
		// set custom icon to image list
		if( hIcon ) return ( ( _iImage = ImageList_AddIcon( hIml, hIcon ) ) == -1 );
		_iImage = 0;
	}
	else
		_iImage = -1;
	return 0;
}

/**
 * name:	OnAddPage
 * class:	CPsTreeItem
 * desc:	inits the treeitem's attributes
 * params:	pPsh	- pointer to the property page's header structure
 *			odp		- OPTIONSDIALOGPAGE structure with the information about the page to add
 * return:	0 on success, 1 on failure
 **/
INT CPsTreeItem::Create( CPsHdr* pPsh, OPTIONSDIALOGPAGE *odp )
{
	INT err;

	// check parameter
	if( !pPsh || pPsh->_dwSize != sizeof( CPsHdr ) || !odp || !odp->hInstance || odp->hInstance == INVALID_HANDLE_VALUE )
		return 1;

	// init page owning contact
	_hContact = pPsh->_hContact;
	_pszProto = mir_strdup( pPsh->_pszProto );
	// global settings prefix for current contact ( is dialog owning contact's protocol by default )
	_pszPrefix = pPsh->_pszPrefix;
	if( !_pszPrefix ) _pszPrefix = "Owner";

	// instance value
	_hInst = odp->hInstance;
	_dwFlags = odp->flags;
	_initParam = odp->dwInitParam;

	TCHAR tszTitle[ MAXSETTING ];

	if( odp->flags & ODPF_USERINFOTAB )
		mir_sntprintf(tszTitle, SIZEOF(tszTitle), _T("%s\\%s"), odp->ptszTitle, odp->ptszTab);
	else
		mir_sntprintf(tszTitle, SIZEOF(tszTitle), _T("%s"), odp->ptszTitle);

		// set the unique utf8 encoded name for the item
	if( err = Name( tszTitle, ( ( _dwFlags & ODPF_UNICODE ) == ODPF_UNICODE ) ) ) {
		MsgErr( NULL, LPGENT( "Creating unique name for a page failed with %d and error code %d" ), err, GetLastError() );
		return 1;
	}

	// read label from database or create it
	if( err = ItemLabel( TRUE ) ) {
		MsgErr( NULL, LPGENT( "Creating the label for a page failed with %d and error code %d" ), err, GetLastError() );
		return 1;
	}

	// load icon for the item
	Icon( pPsh->_hImages, odp, ( pPsh->_dwFlags & PSTVF_INITICONS ) == PSTVF_INITICONS );
	
	// the rest is not needed if only icons are loaded
	if( !( pPsh->_dwFlags & PSTVF_INITICONS ) ) {
		
		// load custom order
		if( !( pPsh->_dwFlags & PSTVF_SORTTREE ) ) {
			_iPosition = ( INT )DB::Setting::GetByte( PropertyKey( SET_ITEM_POS ), odp->position );
		}
		// read visibility state
		_bState =  DB::Setting::GetByte( PropertyKey( SET_ITEM_STATE ), DBTVIS_EXPANDED );

		// error for no longer supported dialog template type
		if( (( DWORD )odp->pszTemplate & 0xFFFF0000 ) ) {
			MsgErr( NULL, LPGENT( "The dialog template type is no longer supported") );
			return 1;
		}
		// fetch dialog resource id
		if( ( _idDlg = ( INT )odp->pszTemplate ) != 0 ) {
			// dialog procedure
			if( ( _pfnDlgProc = odp->pfnDlgProc ) == NULL )
				return 1;

			// lock the property pages dialog resource
			_pTemplate = ( DLGTEMPLATE* )LockResource( LoadResource( _hInst, FindResource( _hInst, ( LPCTSTR )_idDlg, RT_DIALOG ) ) );
			if( !_pTemplate )	return 1;
		}
	}
	return 0;
}

/**
 * name:	DBSaveItemState
 * class:	CPsTreeItem
 * desc:	saves the current treeitem to database
 * param:	pszGroup		- name of the parent item
 *			iItemPosition	- iterated index to remember the order of the tree
 *			iState			- expanded|collapsed|invisible
 *			dwFlags			- tells what to save
 * return:	handle to new ( moved ) treeitem if successful or NULL otherwise
 **/
WORD CPsTreeItem::DBSaveItemState( LPCSTR pszGroup, INT iItemPosition, UINT iState, DWORD dwFlags )
{
	WORD numErrors = 0;

	// save group
	if( ( dwFlags & PSTVF_GROUPS ) && ( dwFlags & PSTVF_POS_CHANGED ) ) {
		numErrors += DB::Setting::WriteUString( PropertyKey( SET_ITEM_GROUP ), ( LPSTR )pszGroup );
	}
	// save label
	if( ( dwFlags & PSTVF_LABEL_CHANGED ) && ( _dwFlags & PSTVF_LABEL_CHANGED ) ) {
		numErrors += DB::Setting::WriteTString( GlobalPropertyKey( SET_ITEM_LABEL ), Label() );
	}
	// save position
	if( ( dwFlags & PSTVF_POS_CHANGED ) && !( dwFlags & PSTVF_SORTTREE ) ) {
		numErrors += DB::Setting::WriteByte( PropertyKey( SET_ITEM_POS ), iItemPosition );
	}
	// save state
	if( dwFlags & PSTVF_STATE_CHANGED ) {
		numErrors += DB::Setting::WriteByte( PropertyKey( SET_ITEM_STATE ), 
			_hItem ? ( ( iState & TVIS_EXPANDED ) ? DBTVIS_EXPANDED : DBTVIS_NORMAL ) : DBTVIS_INVISIBLE );
	}
	RemoveFlags( PSTVF_STATE_CHANGED|PSTVF_LABEL_CHANGED|PSTVF_POS_CHANGED );
	return numErrors;
}

/**
 * name:	CreateWnd
 * class:	CPsTreeItem
 * desc:	create the dialog for the propertysheet page
 * params:	pPs		- propertysheet's datastructure
 *			hDlg	- windowhandle of the propertysheet
 * return:	windowhandle of the dialog if successful
 **/
HWND CPsTreeItem::CreateWnd( LPPS pPs )
{
	if( pPs && !_hWnd && _pTemplate && _pfnDlgProc ) 
	{
		_hWnd = CreateDialogIndirectParam( _hInst, _pTemplate, pPs->hDlg, _pfnDlgProc, ( LPARAM )_hContact );
		if( _hWnd != NULL ) 
		{
			PSHNOTIFY pshn;

			pshn.hdr.code = PSN_PARAMCHANGED;
			pshn.hdr.hwndFrom = _hWnd;
			pshn.hdr.idFrom = 0;
			pshn.lParam = (LPARAM)_initParam;
			SendMessage(_hWnd, WM_NOTIFY, 0, ( LPARAM )&pshn );

			// force child window ( mainly for AIM property page )
			SetWindowLongPtr( _hWnd, GWL_STYLE, ( GetWindowLongPtr( _hWnd, GWL_STYLE ) & ~( WS_POPUP|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME ) ) | WS_CHILD );
			SetWindowLongPtr( _hWnd, GWL_EXSTYLE, GetWindowLongPtr( _hWnd, GWL_EXSTYLE ) & ~( WS_EX_APPWINDOW|WS_EX_STATICEDGE|WS_EX_CLIENTEDGE ) );
			SetParent( _hWnd, pPs->hDlg );

			// move dialog into the display area
			SetWindowPos( _hWnd, HWND_TOP, 
				pPs->rcDisplay.left,	pPs->rcDisplay.top,
				pPs->rcDisplay.right - pPs->rcDisplay.left,	
				pPs->rcDisplay.bottom - pPs->rcDisplay.top,	FALSE );
			// set bold titles
			if( _dwFlags & ODPF_BOLDGROUPS )
			{
				EnumChildWindows( _hWnd, BoldGroupTitlesEnumChildren, ( LPARAM )pPs->hBoldFont );
			}
						
			// some initial notifications
			OnInfoChanged();
			OnPageIconsChanged();
			return _hWnd;
		}
	}
	return NULL;
}

/***********************************************************************************************************
 * public event handlers
 ***********************************************************************************************************/

/**
 * name:	OnInfoChanged
 * class:	CPsTreeItem
 * desc:	Notifies the propertypage of changed contact information
 * params:	none
 * return:	nothing
 **/
VOID CPsTreeItem::OnInfoChanged()
{
	if( _hWnd ) {
		PSHNOTIFY pshn;

		pshn.hdr.code = PSN_INFOCHANGED;
		pshn.hdr.hwndFrom = _hWnd;
		pshn.hdr.idFrom = 0;
		pshn.lParam = ( LPARAM )_hContact;
		if( PSP_CHANGED != SendMessage( _hWnd, WM_NOTIFY, 0, ( LPARAM )&pshn ) ) {
			_dwFlags &= ~PSPF_CHANGED;
		}
	}
}

/**
 * name:	OnPageIconsChanged
 * class:	CPsTreeItem
 * desc:	Notifies the propertypage of changed icons in icolib
 * params:	none
 * return:	nothing
 **/
VOID CPsTreeItem::OnPageIconsChanged()
{
	if( _hWnd ) {
		PSHNOTIFY pshn;

		pshn.hdr.code = PSN_ICONCHANGED;
		pshn.hdr.hwndFrom = _hWnd;
		pshn.hdr.idFrom = 0;
		pshn.lParam = ( LPARAM )_hContact;
		SendMessage( _hWnd, WM_NOTIFY, 0, ( LPARAM )&pshn );
	}
}

/**
 * name:	OnIconsChanged
 * class:	CPsTreeItem
 * desc:	Handles reloading icons if changed by icolib
 * params:	none
 * return:	nothing
 **/
VOID CPsTreeItem::OnIconsChanged( CPsTree *pTree )
{
	HICON hIcon;
	RECT rc;

	// update tree item icons
	if( pTree->ImageList() && ( hIcon = NIcoLib::GetIcon( IconKey() ) ) != NULL ) {
		_iImage = ( _iImage > 0 )
			? ImageList_ReplaceIcon( pTree->ImageList(), _iImage, hIcon )
			: ImageList_AddIcon( pTree->ImageList(), hIcon );
		
		if( _hItem && TreeView_GetItemRect( pTree->Window(), _hItem, &rc, 0 ) ) {
			InvalidateRect( pTree->Window(), &rc, TRUE );
		}
	}
	// update pages icons
	OnPageIconsChanged();
}

} // namespace NContactDetailsPS
